/*
 * Copyright (c) 2007 Compl Yue Still, All Rights Reserved.
 */

package hbi.wire.as3
{
	import flash.events.*;
	import flash.net.Socket;
	import flash.utils.*;
	
	import hbi.domain.*;
	import hbi.domain.as3.*;
	
	import mx.controls.*;
	
	public class ActiveHand
	{
		
		public const HBI_VERSION:int = 0x0100; // version 1.0
		
		private function writeLocalInfo(out:IDataOutput):void
		{
			out.writeUTFBytes("HBI");
			out.writeShort(HBI_VERSION);
			out.writeShort(_localDomain.controllerVersion);
			out.writeUTF(getQualifiedClassName(_localDomain));
			out.writeShort(_requestRemoteDomainControllerVersion);
			out.writeUTF(getQualifiedClassName(_requestRemoteDomainType));
		}
		
		private var _localDomain:DomainController;
		private var _requestRemoteDomainType:Class;
		private var _requestRemoteDomainControllerVersion:int;
		
		private var _socket:Socket;
		private var _onFinish:Function;
		
		public function ActiveHand(
			localDomain:DomainController,
			requestRemoteDomainType:Class,
			requestRemoteDomainControllerVersion:int,
			socket:Socket = null, onFinish:Function = null)
		{
			_localDomain = localDomain;
			_requestRemoteDomainType = requestRemoteDomainType;
			_requestRemoteDomainControllerVersion = requestRemoteDomainControllerVersion;
			
			_socket = socket;
			_onFinish = onFinish;

			if(socket)
			{
				socket.addEventListener(Event.CONNECT, connected);
				socket.addEventListener(ProgressEvent.SOCKET_DATA, readData);
				socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR,
					securityError);
				socket.addEventListener(Event.CLOSE, closed);
				socket.addEventListener(IOErrorEvent.IO_ERROR, ioError);
			}
		}
		
		public function getLocalInfo():ByteArray
		{
			var info:ByteArray = new ByteArray();
			this.writeLocalInfo(info);
			return info;
		}
		
		private function connected(event:Event):void
		{
			this.writeLocalInfo(_socket);
			_socket.flush();
		}
		
		private function closed(event:Event):void
		{
			Alert.show("HBI handshake canceled by peer!", "HBI");
		}

		private function ioError(event:ErrorEvent):void
		{
			Alert.show("HBI handshake failed!\n"
				+ event.text, "HBI");
		}
		
		private function securityError(event:SecurityErrorEvent):void
		{
			Alert.show("HBI connection denied!\n"
				+ event.text, "HBI");
		}
		
		private var _remoteHBIVersion:int = -1;
		
		private var _remoteDomainControllerVersion:int = -1;
		
		private var _remoteDomainTypeNameLength:int = -1;
		
		private function readData(event:Event):void
		{
			const input:IDataInput = _socket;
			
			if(_remoteHBIVersion < 0)
			{
				if(input.bytesAvailable < 5) return;
				if(input.readUTFBytes(3) != "HBI")
				{
					trace("Remote peer does not speak HBI!");
					_socket.close();
					return;
				}
				_remoteHBIVersion = input.readUnsignedShort();
				if(_remoteHBIVersion != HBI_VERSION) 
				// enforce exact match here
				{
					trace("Incompatible HBI version: " + _remoteHBIVersion);
					_socket.close();
					return;
				}
			}
			if(_remoteDomainControllerVersion < 0)
			{
				if(input.bytesAvailable < 2) return;
				_remoteDomainControllerVersion = input.readUnsignedShort();
			}
			if(_remoteDomainControllerVersion != 
				_requestRemoteDomainControllerVersion)
			{
				// if remote domain doesn't support our requested controller
				// version, it should have closed the connection.
				// 
				// assume it is okay here
			}
			if(_remoteDomainTypeNameLength < 0)
			{
				if(input.bytesAvailable < 2) return;
				_remoteDomainTypeNameLength = input.readUnsignedShort();
			}
			if(input.bytesAvailable < _remoteDomainTypeNameLength) return;
			var remoteDomainTypeName:String = input.readUTFBytes(
				_remoteDomainTypeNameLength);
			if(remoteDomainTypeName != 
				getQualifiedClassName(_requestRemoteDomainType))
			{
				// if remote domain doesn't support our requested domain type,
				// it should have closed the connection.
				// 
				// assume it is okay here
				trace("Connected to remote domain: " + remoteDomainTypeName);
			}
			
			_socket.removeEventListener(Event.CONNECT, connected);
			_socket.removeEventListener(ProgressEvent.SOCKET_DATA, readData);
			_socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR,
				securityError);
			_socket.removeEventListener(Event.CLOSE, closed);
			_socket.removeEventListener(IOErrorEvent.IO_ERROR, ioError);
			
			if(_onFinish != null) _onFinish(this);
		}
		
		public function get localDomain():DomainController
		{
			return _localDomain;
		}
		
		public function get socket():Socket
		{
			return _socket;
		}
		
		public function get remoteHBIVersion():int
		{
			return _remoteHBIVersion;
		}
		
		public function get remoteDomainControllerVersion():int
		{
			return _remoteDomainControllerVersion;
		}
		
	}
}
